package com.baranbaygan.downloadmanager
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLStream;
	import flash.utils.ByteArray;

	public class DownloadFile extends EventDispatcher
	{
		
		/**
		 * File name on the server
		 */
		private var fileName:String;
		 
		/**
		 * The URL for the file.
		 */ 
		private var url:String;
		
		/**
		 * This is a string set by the user. This can be used to track which file is which.
		 * Doesn't have anything to do with the internals of the download manager.
		 */ 
		private var fileId:String;
		
		/**
		 * This can be used to understand which file this is. 
		 * (Thumbnail or SmallThumbnail or Main Video, depending on the application using this library)
		 */
		private var externalData:XML; 
		
		/**
		 * fileType is also something the consumer project is going to use.
		 */ 
		private var fileType:String;
		
		private var completedBytes:Number;
		private var totalBytes:Number;
		
		private var urlStream:URLStream;
		
		private var fileDownloadManager:FileDownloadManager;
		private var _downloadPackage:DownloadPackage;
		
		/**
		 * Specifies the current status of the file being downloaded. DownloadStatus final class contains the possible values.
		 */ 
		private var status:String;
		
		
		/*---------------------------------------------------
		* Speed Related
		--------------------------------------------------- */
		/**
		 * Current download speed of the file.
		 */ 
		private var speed:Number=0;		
		/**
		 * We use this array as a history of how many bytes were downloaded each second.
		 * We calculate the avarage of for instance last 5 of these values to find the speed.
		 */ 
		private var downloadSpeedHistory:Array=new Array();
		/**
		 * This is the number of bytes downloaded until the last timer tick.
		 */ 
		private var lastCompletedBytesCount:Number=0;
		/*---------------------------------------------------
		* END OF Speed Related
		--------------------------------------------------- */		
		
		
		
		private var fileStream:FileStream;
		/**
		 * This file is in the temporary download folder.
		 * We should create it if it doesn't exist
		 */ 
		private var tempFile:File;
		
		private var byteBuffer:ByteArray = new ByteArray();
		
		
		/* ------------------------------------------
		
		Setter Getters
		
		------------------------------------------ */
		
		public function get ExternalData():XML
		{
			return this.externalData;
		}
		public function get FileExtension():String
		{
			return this.fileName.substring(this.fileName.lastIndexOf("."), this.fileName.length);
		}
		public function get Speed():Number
		{
			return this.speed;
		}
		public function set Speed(value:Number):void
		{
			this.speed=value;
		}

		public function set Status(value:String):void
		{
			this.status=value;
		}
		public function get Status():String
		{
			return this.status;
		}
		public function get FileName():String
		{
			return this.fileName;
		}
		public function get Url():String
		{
			return this.url;
		}
		public function get FileId():String
		{
			return this.fileId;
		}
		public function get FileType():String
		{
			return this.fileType;
		}
		public function get CompletedBytes():Number
		{
			return this.completedBytes;
		}
		public function get TotalBytes():Number
		{
			return this.totalBytes;
		}
		
		/**
		 * Constructor takes the xml node of the related file in the download tracker file as a parameter.
		 */ 
		public function DownloadFile(xml:XML, downloadManager:FileDownloadManager, downloadPackage:DownloadPackage)
		{
			super(null);

			this.fileDownloadManager = downloadManager;
			this._downloadPackage = downloadPackage;

			this.fileName = xml.FileName;
			this.fileId = xml.FileId;
			this.fileType = xml.FileType;
			this.url = xml.Url;
			this.status = xml.Status;
			this.completedBytes = Number(xml.CompletedBytes);
			this.totalBytes = Number(xml.TotalBytes);
			this.externalData = XML(xml.ExternalData[0]);
			
			this.urlStream = new URLStream();
			
			configureListeners(this.urlStream);
			
			if(xml.Status == DownloadStatus.DOWNLOADING || xml.Status == DownloadStatus.STARTING)
				this.StartDownload();
		}

        private function configureListeners(dispatcher:EventDispatcher):void 
        {
            dispatcher.addEventListener(Event.COMPLETE, completeHandler);
            dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
            dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            dispatcher.addEventListener(Event.OPEN, openHandler);
            dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
            dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
        }
		
		/**
		 * We create custom range header.
		 * Range header tells the server where we want to resume the download from.
		 */
		private function CreateUrlRequestHeader():URLRequestHeader
		{
			//var header:URLRequestHeader = new URLRequestHeader("range","bytes="+xml.completedBytes+"-"+xml.totalBytes);
			var header:URLRequestHeader = new URLRequestHeader("range","bytes="+this.completedBytes+"-"+this.totalBytes);
			return header;
		}
		
		/*
        private function isCompressed():Boolean {
            return (stream.readUTFBytes(3) == ZLIB_CODE);
        }
        */

        private function completeHandler(event:Event):void 
        {
            var newFile:File = new File(this.fileDownloadManager.DownloadFolder + "/" + this._downloadPackage.PackageID + "/" + this.fileName);
            var fileStream:FileStream = new FileStream();
            fileStream.open(newFile, FileMode.WRITE);
            var contents:ByteArray = new ByteArray();
            this.urlStream.readBytes(contents);
            fileStream.writeBytes(contents);
            fileStream.close();
            
            this.status = DownloadStatus.COMPLETED;
            
            this.dispatchEvent(event);
        }

        private function openHandler(event:Event):void 
        {
        	this.status = DownloadStatus.DOWNLOADING;
        	
        	this.dispatchEvent(event);
        }

        private function progressHandler(event:ProgressEvent):void 
        {
        	this.status = DownloadStatus.DOWNLOADING;

        	this.totalBytes = event.bytesTotal;
        	this.completedBytes = event.bytesLoaded;
        	
        	this.dispatchEvent(event);
        }

        private function securityErrorHandler(event:SecurityErrorEvent):void 
        {
            this.status = DownloadStatus.DOWNLOAD_ERROR;
        }

        private function httpStatusHandler(event:HTTPStatusEvent):void {
            
        }

        private function ioErrorHandler(event:IOErrorEvent):void 
        {
        	this.status = DownloadStatus.DOWNLOAD_ERROR;
        }
        
		/* -------------------------------------------------------
		*	Public functions
		* ------------------------------------------------------- */
		
		public function CopyFile(path:String):Boolean
		{
			if(this.status!=DownloadStatus.COMPLETED)
			{
				return false;
			}
			
			var fileOnDisk:File = new File(this.fileDownloadManager.DownloadFolder + "/" + this._downloadPackage.PackageID + "/" + this.fileName);
			
			var newFile:File = File.applicationStorageDirectory.resolvePath(path + this.fileName);
			
			fileOnDisk.copyTo(newFile, true);
			
			return true;
		}
		
		public function DeleteDownloadedFile():void
		{
			var fileOnDisk:File = new File(this.fileDownloadManager.DownloadFolder + "/" + this._downloadPackage.PackageID + "/" + this.fileName);
			fileOnDisk.deleteFileAsync();
		}
		
		/**
		 * This is the event handler of the timer in the FileDownloadManager class.
		 * Each download file calculates its own speed data when the timer ticks.
		 */
		public function Timer_Tick():void
		{
			if(this.status==DownloadStatus.DOWNLOADING)
			{
				var downloadedByteCount:Number = this.completedBytes - this.lastCompletedBytesCount;
				this.lastCompletedBytesCount = this.completedBytes;
				
				// Check if downloadedByteCount is positive.
				// The XML file may indicate a number more than the 
				// currently downloaded bytes when the file is coming from the cache.
				if(downloadedByteCount > 0)
				{
					this.downloadSpeedHistory.push(downloadedByteCount);
					var countOfTicks:Number=5; // Take the last 5 values to calculate the speed.
					if(this.downloadSpeedHistory.length==countOfTicks+1)
						this.downloadSpeedHistory.shift();

					var downloadBytesSum:Number = 0;
					
					for (var i:Number=0; i<this.downloadSpeedHistory.length; i++)
					{
						downloadBytesSum += this.downloadSpeedHistory[i];
					}
					var avarageBytes:Number = downloadBytesSum / countOfTicks;
					
					this.speed = (avarageBytes / FileDownloadManager.SpeedTimerInterval * 1000) / 1024; // Calculate the speed in kb
				}
			}
		}
		
		public function PauseDownload():void
		{
			this.status = DownloadStatus.PAUSED;
			
			if(this.urlStream.connected)
				this.urlStream.close();
		}
		
		public function StartDownload():void
		{
			this.status = DownloadStatus.DOWNLOADING;
			
			var urlRequest:URLRequest = new URLRequest(this.url);
			//urlRequest.useCache=false;
			this.urlStream.load(urlRequest);
		}
		
		public function CancelDownload():void
		{
			if(this.urlStream.connected)
				this.urlStream.close();
		}
	}
}



